package com.smp.utils;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
import com.smp.entity.PayloadDto;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;


/**
 * 用户token的工具类
 *
 * @author YangYi
 * @version v1.0
 * @date 2019 /3/31 14:55
 * @since 1.0.0
 */
@Slf4j
public class TokenUtils {
    /**
     * header中的token名称
     */
    public static final String HEADER_TOKEN = "Authorization";
    /**
     * 失效时间：单位分钟
     */
    public static final int EXPIRE_TIME = 480;
    /**
     * 请求参数中的token名称
     */
    private static final String PARAM_TOKEN = "token";
    /**
     * Exp
     */
    private static final String KEY_EXP = "exp";

    /**
     * The constant KEY_BEARER.
     */
    private static final String KEY_BEARER = "Bearer";

    /**
     * 私钥
     */
    private static final byte[] SECRET = "464c598aa7ce410f915884ee86e94e7c".getBytes();


    /**
     * 生成一个token
     *
     * @param payloadMap the payload map
     * @return string string
     * @throws JOSEException the jose exception
     */
    public static String createTokenHS256(Map<String, Object> payloadMap) throws JOSEException {
        /*
          JWSHeader参数：1.加密算法法则,2.类型，3.。。。。。。。
          一般只需要传入加密算法法则就可以。
          这里则采用HS256

          JWSAlgorithm类里面有所有的加密算法法则，直接调用。
         */
        JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.HS256);
        //建立一个载荷Payload
        Payload payload = new Payload(new JSONObject(payloadMap));
        //将头部和载荷结合在一起
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        //建立一个密匙
        JWSSigner jwsSigner = new MACSigner(SECRET);
        //签名
        jwsObject.sign(jwsSigner);
        //生成token
        return jwsObject.serialize();
    }


    /**
     * Creat token rs 256 string.
     *
     * @param payloadMap the payload map
     * @param rsaJwk     the rsa jwk
     * @return the string
     * @throws JOSEException the jose exception
     */
    public static String creatTokenRS256(Map<String, Object> payloadMap, RSAKey rsaJwk) throws JOSEException {

        //私密钥匙
        JWSSigner signer = new RSASSASigner(rsaJwk);

        JWSObject jwsObject = new JWSObject(
                new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(rsaJwk.getKeyID()).build(),
                new Payload(new JSONObject(payloadMap))
        );
        //进行加密
        jwsObject.sign(signer);

        return jwsObject.serialize();
    }


    /**
     * Valid rs 256 token result.
     * 验证token
     *
     * @param token  the token
     * @param rsaJwk the rsa jwk
     * @return the token result
     * @throws JOSEException the jose exception
     */
    public static TokenResult validRS256(String token, RSAKey rsaJwk) throws JOSEException, java.text.ParseException {
        //获取到公钥
        RSAKey rsaKey = rsaJwk.toPublicJWK();
        JWSObject jwsObject = JWSObject.parse(token);
        JWSVerifier jwsVerifier = new RSASSAVerifier(rsaKey);
        //验证数据
        return verify(jwsObject, jwsVerifier);
    }


    /**
     * 验证token 是否有效
     *
     * @param token the token
     * @return map token result
     */
    public static TokenResult validRS256(String token) {
        TokenResult result = new TokenResult();
        if (StringUtils.isBlank(token)) {
            return result;
        }
        try {
            //解析token
            JWSObject jwsObject = JWSObject.parse(token);
            //建立一个解锁密匙
            JWSVerifier jwsVerifier = new MACVerifier(SECRET);
            result = verify(jwsObject, jwsVerifier);
        } catch (Exception e) {
            log.debug("valid token error.", e);
        }
        return result;
    }

    /**
     * Verify token result.
     *
     * @param jwsObject   the jws object
     * @param jwsVerifier the jws verifier
     * @return the token result
     * @throws JOSEException the jose exception
     */
    private static TokenResult verify(JWSObject jwsObject, JWSVerifier jwsVerifier) throws JOSEException {
        TokenResult result = new TokenResult();
        result.setState(TokenStateEnum.FAILURE);
        //获取到载荷
        Payload payload = jwsObject.getPayload();
        //判断token
        if (jwsObject.verify(jwsVerifier)) {
            result.setState(TokenStateEnum.SUCCESS);
            //载荷的数据解析成json对象。
            Map<String, Object> stringObjectMap = payload.toJSONObject();
            log.info(stringObjectMap.toString());
            PayloadDto jsonObject = JSONUtil.toBean(payload.toString(), PayloadDto.class);
            result.setData(jsonObject);
        } else {
            result.setState(TokenStateEnum.FAILURE);
        }
        return result;
    }


    /**
     * 从请求中获得token
     * <p>
     * 1.header:Authorization
     * 2.request parameter:token
     * 3. Bearer token:Authorization
     * <p>
     *
     * @param httpRequest the http request
     * @return string string
     */
    public static String extractAuthTokenFromRequest(HttpServletRequest httpRequest) {
        /* Get token from header */
        String authToken = httpRequest.getHeader(HEADER_TOKEN);

        /* If token not found get it from request parameter */
        if (authToken == null) {
            authToken = httpRequest.getParameter(PARAM_TOKEN);
        } else {
            if (authToken.startsWith(KEY_BEARER)) {
                authToken = authToken.substring(7);
            }
        }


        if (authToken == null) {
            String[] authTokens = httpRequest.getParameterValues(HEADER_TOKEN);
            if (authTokens != null && authTokens.length > 0) {
                authToken = authTokens[0];
            }
        }

        return authToken;
    }


    /**
     * 创建加密key
     *
     * @return the key
     * @throws JOSEException the jose exception
     */
    public static RSAKey getKey() throws JOSEException {
        RSAKeyGenerator rsaKeyGenerator = new RSAKeyGenerator(2048);
        return rsaKeyGenerator.generate();
    }

    /**
     * The enum Token result enum.
     *
     * @author YangYi
     * @version v1.0
     * @date 2020 /05/09
     */
    public enum TokenStateEnum {
        /**
         * 成功
         */
        SUCCESS,
        /**
         * 失败
         */
        FAILURE,
        /**
         * 超时
         */
        OVERTIME
    }

    /**
     * The type Token result.
     *
     * @author fengyuchenglun
     * @version 1.0.0
     * @date 2020 /05/09
     */
    @Data
    public static class TokenResult {
        /**
         * The State.
         */
        private TokenStateEnum state = TokenStateEnum.FAILURE;
        /**
         * The Data.
         */
        private Object data;
    }

}
